/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import { boxMessage, unboxMessage } from '@ironfish/rust-nodejs'
import { Assert } from '../../assert'
import { Blockchain } from '../../blockchain'
import { Identity, PrivateIdentity, privateIdentityToIdentity } from '../identity'
import { IdentifyMessage } from '../messages/identify'
import { IsomorphicWebSocketConstructor } from '../types'

/**
 * Wraps configuration needed for establishing connections with other peers
 * and maintains references to all known peers.
 */
export class LocalPeer {
  readonly chain: Blockchain
  // our keypair for encrypting messages
  readonly privateIdentity: PrivateIdentity
  // the identity we expose to other peers
  readonly publicIdentity: Identity
  // the agent of the local client
  readonly agent: string
  // the protocol version of the local client
  readonly version: number
  // constructor for either a Node WebSocket or a browser WebSocket
  readonly webSocket: IsomorphicWebSocketConstructor
  // the unique ID number of the network
  readonly networkId: number
  // true if the peer supports syncing and gossip messages
  readonly enableSyncing: boolean

  // optional port the local peer is listening on
  port: number | null
  // optional a human readable name for the node
  name: string | null

  constructor(
    identity: PrivateIdentity,
    agent: string,
    version: number,
    chain: Blockchain,
    webSocket: IsomorphicWebSocketConstructor,
    networkId: number,
    enableSyncing: boolean,
  ) {
    this.privateIdentity = identity
    this.publicIdentity = privateIdentityToIdentity(identity)
    this.chain = chain
    this.agent = agent
    this.version = version
    this.networkId = networkId
    this.enableSyncing = enableSyncing

    this.webSocket = webSocket
    this.port = null
    this.name = null
  }

  /**
   * Construct an Identify message with our identity and version.
   */
  getIdentifyMessage(): IdentifyMessage {
    Assert.isNotNull(this.chain.head, 'Cannot connect to the network without a genesis block')

    return new IdentifyMessage({
      agent: this.agent,
      head: this.chain.head.hash,
      identity: this.publicIdentity,
      name: this.name || undefined,
      port: this.port,
      sequence: Number(this.chain.head.sequence),
      version: this.version,
      work: this.chain.head.work,
      networkId: this.networkId,
      genesisBlockHash: this.chain.genesis.hash,
      features: {
        syncing: this.enableSyncing,
      },
    })
  }

  /**
   * Encrypt a string for recipient with the stored private identity.
   * @param plainTextMessage The string to encrypt.
   * @param recipient The public key of the recipient of the message.
   */
  boxMessage(
    plainTextMessage: string,
    recipient: Identity,
  ): { nonce: string; boxedMessage: string } {
    return boxMessage(plainTextMessage, this.privateIdentity.secretKey, recipient)
  }

  /**
   * Decrypt a message using a nonce from a sender.
   * @param boxedMessage An encrypted message string.
   * @param nonce A nonce, generated by boxMessage.
   * @param sender The public key of the message sender.
   */
  unboxMessage(
    boxedMessage: string,
    nonce: string,
    sender: Identity,
  ): { message: string | null } {
    let message
    try {
      message = unboxMessage(boxedMessage, nonce, sender, this.privateIdentity.secretKey)
    } catch {
      message = null
    }

    return { message }
  }
}
